
//
//  MgTokenMamanager.swift
//  TokenManagerDemo
//
//  Created by lyj on 2025-08-04.
//

import Foundation

protocol MgTokenSynchronizer {
	func sync<T: Sendable>(_ operation: @escaping @Sendable () throws -> T) throws -> T
	func syncAsync<T: Sendable>(_ operation: @escaping @Sendable () async throws -> T) async throws -> T
}

@available(iOS 15, *)
actor MgActorTokenSynchronizer: @preconcurrency MgTokenSynchronizer {
	
	private var isRefreshing = false
	private var waitingContinuations: [CheckedContinuation<Void, Error>] = []
	
	public init() {
		
	}
	func sync<T: Sendable>(_ operation: @escaping @Sendable () throws -> T) throws -> T {
		try operation()
	}
	
	func syncAsync<T: Sendable>(_ operation: @escaping @Sendable () async throws -> T) async throws -> T {
		if isRefreshing {
			// 如果正在刷新，挂起等待
			try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
				waitingContinuations.append(continuation)
			}
			// 等待结束后 return，其他任务已经完成刷新了
			return try await operation()
		}
		
		// 当前为第一个发起刷新请求的任务
		isRefreshing = true
		defer {
			// 清空等待队列，恢复所有挂起任务
			let continuations = waitingContinuations
			waitingContinuations = []
			isRefreshing = false
			for c in continuations {
				c.resume()
			}
		}
		
		return try await operation()
	}
}

final class MgGCDTokenSynchronizer: MgTokenSynchronizer {
	
	private let queue:  DispatchQueue
	
	init(label: String) {
		self.queue = DispatchQueue(label: label)
	}
	
	func sync<T: Sendable>(_ operation: @escaping @Sendable () throws -> T) throws -> T {
		try queue.sync {
			try operation()
		}
	}
	
	func syncAsync<T: Sendable>(_ operation: @escaping @Sendable () async throws -> T) async throws -> T {
		try await withCheckedThrowingContinuation { continuation in
			queue.async {
				Task  {
					do {
						let result = try await operation()
						continuation.resume(returning: result)
					}catch {
						continuation.resume(throwing: error)
					}
				}
			}
		}
	}
}

/// 提供 Token 相关逻辑的协议，用于网络请求中的鉴权处理。
///
/// ### 📌 使用流程说明：
/// `MgTokenProvider` 是 `MgTokenManager` 的依赖协议，用于将你的 Token 获取、刷新、注入逻辑解耦并注入拦截器框架中，适配你自己的业务需求。
///
/// 通常与 `MgTokenManager` 一起使用，调用流程如下：
///
/// 1. 发送网络请求前，调用 `requiresValidToken(_:)` 判断是否需要鉴权；
/// 2. 如果需要鉴权，则调用 `isTokenValid()` 判断 Token 是否有效；
/// 3. 如果 Token 已失效，则调用 `refreshToken()` 尝试刷新 Token；
/// 4. 刷新成功后，调用 `authorized(_:)` 注入最新的 Token（如加入 HTTP Header）；
/// 5. 若请求返回 401、403 或其他错误，通过 `shouldRefreshToken(...)` 判断是否需要刷新 Token 并重试；
///
/// ⚠️ 该协议为使用者提供最大灵活性，特别是：
///		— 可以自定义 token 的存储/刷新策略；
///		— 可以定义哪些请求需要鉴权；
///		— 可以基于响应数据内容判断是否刷新 Token，而不是仅依赖 HTTP 状态码。
///
/// 请确保所有方法是线程安全的，异步方法推荐使用 `actor` 或其他同步手段保护 Token 状态。
///
public protocol MgTokenProvider: Sendable {
	
	/// 最大刷新 Token 重试次数
	///
	/// 用于限制在 token 刷新失败时，最多尝试重新刷新 token 的次数，防止无限重试导致死循环。
	/// 一般建议设置为 2～3 次，根据网络状况和业务需求调整。
	///
	/// 该值主要用于控制 `performAuthorizedRequest` 中遇到 token 失效后，自动刷新 token 的最大重试次数。
	var maxRefreshRetryCount: Int { get }
	
	/// Token 刷新重试的时间间隔（纳秒）
	///
	/// 每次刷新失败后，等待指定时间再重试，避免短时间内频繁请求服务器。
	/// 可以根据实际需求调整为固定间隔或指数退避。
	///
	/// 例如：500_000_000 表示 0.5 秒。
	var refreshRetryDelayNanoseconds: UInt64 { get }
	
	/// 判断请求是否需要携带有效 token
	///
	/// - Parameter request: 即将发出的请求
	/// - Returns: `true` 表示该请求需要有效的 token 才能发出；`false` 表示不需要 token（如登录、注册请求）
	func requiresValidToken(_ request: URLRequest) async -> Bool
	
	
	/// 判断当前 token 是否有效
	///
	/// - Returns: `true` 表示 token 仍然有效；`false` 表示已过期或无效，需要刷新。
	func isTokenValid() async -> Bool
	
	/// 刷新 token
	/// 在该方法中实现刷新 token的方法，token自己缓存
	///
	/// 当 token 无效时调用，用于获取新的 token。
	/// - Throws: 如果刷新失败，应抛出对应错误（例如未登录、网络异常等）。
	func refreshToken() async throws
	
	
	/// 为请求注入 token（通常放入请求头中）
	/// 触发时机
	/// 	token 验证完后，把新的token写入到request
	///
	/// - Parameter request: 原始请求
	/// - Returns: 注入 token 后的新请求
	/// - Throws: 当 token 注入失败（例如 token 缺失）时，可抛出错误
	func authorized(_ request: URLRequest) async throws -> URLRequest
	
	/// 判断是否需要刷新 token 并重试请求
	///
	/// 可用于处理服务端返回 "token 过期" 或 "无权限" 错误场景。
	/// 通常结合 `response.statusCode` 或返回数据中的错误码判断。
	/// 	例如 response.statusCode == 401
	/// 		return true，表示需要刷新 token，那么拦截器会触发  refreshToken()
	/// 		return false, 表示接口正常访问，无需刷新 token
	/// 	这里提供外部接口的原因，不同的接口设计，对重置token的条件不一样，开放该接口，可支持用户灵活处理刷新token 的条件
	/// 		例如：
	/// 			根据 data 中的错误码或其他信息判定是否需要刷新  token 而不是仅仅 statusCode == 401
	/// 			或者 statusCode == 401 || statusCode == 403
	///
	/// - Parameters:
	///   - request: 原始请求
	///   - response: URL 响应，可为 nil
	///   - data: 响应数据内容，可为 nil
	func shouldRefreshToken(
		request: URLRequest,
		response: URLResponse?,
		data: Data?
	) async -> Bool
	
}

enum MgTokenManagerError: Error, LocalizedError {
	/// 在接口请求过程中，若检测到 Token 失效会触发刷新逻辑。
	/// 若刷新后的新 Token 依然因服务端错误（例如颁发的 Token 本身无效）导致再次失效，
	/// 则可能陷入：请求失败 → 刷新 Token → 请求重试 → 再次失败 的死循环。
	/// 为避免此类情况，系统会限制刷新 Token 的最大重试次数。
	case refreshTokenRetryExceeded
	
	var errorDescription: String? {
		switch self {
			case .refreshTokenRetryExceeded:
				return "刷新 Token 超过最大次数"
		}
	}
}

/// 加了 actor ，会自动保证该对象访问在多线程中是安全的，不会出现数据竞争
/// 使用的时候，在外部创建一个单利使用
public final class MgTokenManager {
	
	private let provider: MgTokenProvider
	///  消息队列，入列的任务会挨个 执行，例如同步发起n个请求，
	private let synchronizer: MgTokenSynchronizer
	
	/// 用于模拟网络请求延迟，仅用于测试（单位：纳秒）
	public var mockRequestDelayNanosecondsRange: ClosedRange<UInt64>? = nil
	
	@available(iOS 15, macOS 12, *)
	public init(provider: MgTokenProvider) {
		self.provider = provider
		self.synchronizer = MgActorTokenSynchronizer()
	}
	
	public init(provider: MgTokenProvider, disqueueLabel: String = "com.GCDTokenSynchronizer.token") {
		self.provider = provider
		if #available(iOS 15, macOS 12, *) {
			self.synchronizer = MgActorTokenSynchronizer()
		}else {
			self.synchronizer = MgGCDTokenSynchronizer(label: disqueueLabel)
		}
	}
	
	
	/// 校验请求所需的 Token 是否有效
	///
	/// - 如果 `provider.requiresValidToken` 返回 true，表示该请求需要验证 Token。
	/// - 接着通过 `provider.isTokenValid` 判断当前 Token 是否仍然有效。
	/// - 若 Token 已失效，则通过 `provider.refreshToken` 获取新的有效 Token。
	///
	/// - Parameter request: 待校验的请求
	public func validToken(for request: URLRequest) async throws {
		
		guard await provider.requiresValidToken(request) else { return }
		guard await !provider.isTokenValid() else { return }
		
		let provider = self.provider
		try await synchronizer.syncAsync {
			
			// 二次判断，防止等待期间已被他人刷新，这里即使不添加也没太大关系，如果期间数据 被强行更改，无非造成的结果就是token 无效，触发下面的重刷token机制，如果超出设置的次数，那么接口直接返回错误信息
			guard await provider.requiresValidToken(request) else { return }
			guard await !provider.isTokenValid() else { return }
			
			var retryCount = 0
			
			while true {
				
				do {
					try await provider.refreshToken()
					break // 刷新成功，跳出循环
				}catch {
					retryCount += 1
					if retryCount >= provider.maxRefreshRetryCount {
						throw error
					}
					
					try await Task.sleep(nanoseconds: provider.refreshRetryDelayNanoseconds)
				}
			}
		}
	}
	
	/// 构造已授权的请求（如有必要自动刷新 Token）
	///
	/// 此方法将自动校验请求是否需要 Token，如果需要：
	/// - 若当前 Token 有效，直接将其注入请求头中；
	/// - 若当前 Token 已过期，则自动刷新 Token 后再注入；
	///
	/// 如果该请求不需要验证 Token，则会原样返回请求。
	///
	/// - Parameter request: 原始 `URLRequest` 请求
	/// - Returns: 注入了有效 Token 的请求
	/// - Throws: 如果 Token 刷新失败，抛出对应错误
	public func authorizedRequest(for request: URLRequest) async throws -> URLRequest {
		try await validToken(for: request)
		return try await provider.authorized(request)
	}
	
	/// 发起带 Token 的授权请求，支持自动刷新 Token 并重试
	///
	/// 本方法会根据请求需要，自动注入有效的 Token 进行请求。
	/// 如果遇到服务器返回需要刷新 Token 的响应（如 401 Unauthorized），
	/// 会自动刷新 Token 并重试请求，最多重试 `maxRetryCount` 次。
	///
	/// - 参数 request: 待发起的原始 URLRequest，无需自行注入 Token，本方法会自动处理授权逻辑。
	///
	/// - 参数 maxRetryCount: 请求失败时的最大重试次数，默认 1，表示最多重试一次。
	///
	/// - 参数 retryDelayNanoseconds: 每次重试之间的延迟时间，单位为纳秒，默认 0.5 秒。
	///
	/// - 参数 maxRefreshTokenRetryCount: token 刷新重试次数，默认 1，防止刷新后无效死循环。
	///   	-  用于防止刷新后的 Token 本身依然无效的情况导致死循环。
	///   		-  典型场景如：接口返回 401 → 刷新 Token → 重试请求 → 再次 401 → 无限循环。
	///   		- 此类问题多为后端颁发了一个无效的 Token（如已过期或未生效），需要前端在此处进行保护。
	///
	/// - 参数 session: 用于发起网络请求的 URLSession，默认使用 `.shared`。
	///
	/// - 返回: 请求成功时返回服务器响应的数据和响应对象 `(Data, URLResponse)`。
	///
	/// - 抛出: 请求失败时抛出对应错误，如网络异常、Token 刷新失败或重试次数耗尽。
	public func performAuthorizedRequest(
		_ request: URLRequest,
		maxRetryCount: Int = 1,
		retryDelayNanoseconds: UInt64 = 500_000_000,
		maxRefreshTokenRetryCount: Int = 1,
		using session: URLSession =  .shared
	) async throws  -> (Data, URLResponse) {
		
		var retryCount = 0
		var refreshTokenRetryCount = 0
		
		while true {
			do {
				
				let authorizedRequest = try await authorizedRequest(for: request)
				
				let (data, response): (Data, URLResponse)
				
				if let delayRange = mockRequestDelayNanosecondsRange {
					// 👇 模拟网络请求延迟
					let delay = UInt64.random(in: delayRange)
					try await Task.sleep(nanoseconds: delay)
					
					// 👇 模拟返回的数据
					let dummyJSON: [String: Any] = [
						"code": 200,
						"message": "Mock success",
						"data": ["result": true]
					]
					data = try JSONSerialization.data(withJSONObject: dummyJSON, options: [])
					
					// 👇 模拟响应
					response = HTTPURLResponse(
						url: authorizedRequest.url!,
						statusCode: 200,
						httpVersion: nil,
						headerFields: ["Content-Type": "application/json"]
					)!
				} else {
					// 👇 实际网络请求
					(data, response) = try await session.data(for: authorizedRequest)
				}
				if await provider.shouldRefreshToken(request: request, response: response, data: data) {
					refreshTokenRetryCount += 1
					if refreshTokenRetryCount > maxRefreshTokenRetryCount {
						throw MgTokenManagerError.refreshTokenRetryExceeded
					}
					
					try await validToken(for: request)
					continue
				}
				
				
				// 请求成功且无需刷新 token，直接返回
				return (data, response)
				
			} catch {
				
				retryCount += 1
				if retryCount >= maxRetryCount {
					throw error
				}
				try? await Task.sleep(nanoseconds: retryDelayNanoseconds)
			}
		}
	}
}
